package se.webbzon.boltzmann.game.object.platform;

import javax.vecmath.Point3d;

import se.webbzon.boltzmann.game.object.waypoint.PathMovable;
import se.webbzon.boltzmann.game.object.waypoint.PathProgram;
import se.webbzon.boltzmann.game.object.waypoint.PathProgramEnum;
import se.webbzon.boltzmann.game.object.waypoint.Waypoint;
import se.webbzon.boltzmann.game.switchable.Switchable;
import se.webbzon.boltzmann.object.geometry.GeometryAppearance;
import se.webbzon.boltzmann.object.geometry.GeometryMask;
import se.webbzon.boltzmann.object.geometry.MaskedGeometry;
import se.webbzon.oschi01.collision3d.CollisionNode3D;
import se.webbzon.oschi01.friendly.Friendly;
import se.webbzon.oschi01.path3d.Path3D;
import se.webbzon.oschi01.worldarea.WorldArea;
import se.webbzon.oschi01.worldeditor.DeflatableWorldObject;
import se.webbzon.oschi01.worldeditor.WorldObjectDeflation;
import se.webbzon.oschi01.worldobject.WorldMovableObject;
import se.webbzon.oschi01.worldobject.WorldObject;
import se.webbzon.oschi01.worldobject.appearance.Appearance3DException;
import se.webbzon.oschi01.worldobject.appearance.MutableAppearance3D;

public class Platform extends WorldMovableObject implements MaskedGeometry, Switchable, DeflatableWorldObject, PathMovable {
	
	// The name of the deflatable world object
	public static final String worldObjectName = "platform";
	
	private enum State {PLAY, STOP, REWIND};
	
	// A point used to calculate the velocity of the platform
	private final Point3d dP;
	
	// The state of the platform
	private State state;
	
	// The current time and the duration of a program cycle
	private int time, duration;
	
	// The path which the platform will follow
	private Path3D path;	
	
	// The waypoint used to generate the path of the platform
	private Waypoint waypoint;
	
	// The current program enumeration of the platform
	private PathProgramEnum programEnum;
	private PathProgram program;
	
	// The appearance and mask used for this platform
	private GeometryAppearance appearance;
	private GeometryMask mask;
	
	// The shadow of the platform
	private PlatformShadow shadow;
	
	/*============================================================
	Constructors
	============================================================*/
	
	public Platform(GeometryAppearance appearance, boolean visible,
					GeometryMask mask, boolean solid, 
					boolean generateShadow,
					PathProgramEnum programEnum, Waypoint waypoint)
									throws Appearance3DException {
		super(	WorldObject.onCreate + WorldObject.onStep + WorldObject.onDestroy,
				new MutableAppearance3D(appearance.getAppearance()));
		this.appearance = appearance;
		setGeometryMask(mask);
		setVisible(visible);
		setSolid(solid);
		setProgram(programEnum);
		setWaypoint(waypoint);
		
		dP = new Point3d();
		time = 0;
		duration = 1;
		state = State.PLAY;
		
		if (generateShadow)
			new PlatformShadow(this,PlatformShadowShape.SQUARE);
	}
	
	/*============================================================
	Public Methods
	============================================================*/
	
	/** Sets the path of this platform by using the given waypoint. **/
	public void setWaypoint(Waypoint waypoint) {
		this.waypoint = waypoint;
		if (waypoint != null)
			path = waypoint.makePath3D();
	}
	
	/** Returns the waypoint used to create the path of the platform. **/
	public Waypoint getWaypoint() {
		return waypoint;
	}
	
	/** Sets the duration (in number of steps) of a full program cycle. 
	 * The duration must be greater or equal to 1 step. **/
	public void setCycleDuration(int numberOfSteps) {
		duration = (numberOfSteps < 1) ? 1 : numberOfSteps;
		time = (duration < time) ? duration : time;
	}
	
	@Override public int getCycleDuration() {
		return duration;
	}
	
	/** Sets the program enumeration of the platform. **/
	public void setProgram(PathProgramEnum programEnum) {
		this.programEnum = programEnum;
		program = programEnum.getProgram();
	}
	
	/** Returns the program enumeration of the platform. **/
	public PathProgramEnum getProgram() {
		return programEnum;
	}
	
	@Override public void play() {
		state = State.PLAY;
	}
	
	@Override public void stop() {
		state = State.STOP;
	}
	
	@Override public void rewind() {
		state = State.REWIND;
	}
	
	@Override public boolean isPlaying() {
		return (state == State.PLAY);
	}
	
	@Override public boolean isRewinding() {
		return (state == State.REWIND);
	}
	
	@Override public void seek(int time) {
		this.time = (duration < time) ? duration : time;
	}
	
	@Override public int pos() {
		return time;
	}
	
	/** Returns the path associated with this platform instance. **/
	public Path3D getPath() {
		return path;
	}
	
	/** Returns the shadow of this platform. **/
	public PlatformShadow getShadow() {
		return shadow;
	}
	
	@Override public void setGeometryAppearance(GeometryAppearance appearance) 
			throws Appearance3DException {
		((MutableAppearance3D) getAppearance()).setAppearance(appearance.getAppearance());
		this.appearance = appearance;
	}
	
	@Override public void setGeometryMask(GeometryMask mask) {
		if (mask == null)
			super.setMask(null);
		else
			super.setMask(mask.getMask());
		this.mask = mask;
	}
	
	@Override public GeometryAppearance getGeometryAppearance() {
		return appearance;
	}
	
	@Override public GeometryMask getGeometryMask() {
		return mask;
	}
	
	/** Not supported by platforms. **/
	@Override public void setMask(CollisionNode3D mask) {
		return;
	}
	
	@Override public void onCreate(WorldArea world) {
		if (shadow != null)
			shadow.launch(world);
	}
	
	@Override public void onDestroy(WorldArea world) {
		if (shadow != null)
			shadow.terminate(world);
	}
	
	@Override public void onStep(WorldArea world) {
		if (	(state == State.PLAY || state == State.REWIND) &&
				path != null && program != null) {
			final double position = program.run(this);
			path.getPoint(position, dP);
			dP.sub(getPosition());
			setVelocity(dP.x,dP.y,dP.z);

			if (state == State.PLAY) {
				if (time < duration)
					time++;
				else
					stop();
			} else {
				if (time > 0)
					time--;
				else
					stop();
			}
		} else
			setVelocity(0,0,0);
	}
	
	@Override public void setEnable(boolean enable, boolean instant) {
		if (enable && !instant)
			play();
		else {
			stop();
			if (instant)
				seek(enable ? getCycleDuration() : 0);
		}
	}

	@Override public boolean isEnabled() {
		return isPlaying() || isRewinding();
	}

	@Override public String getName() {
		return worldObjectName;
	}

	@Override public WorldObjectDeflation getDeflation() {
		return new PlatformDeflation(0,this,waypoint);
	}
	
	/*============================================================
	Friendly Methods
	============================================================*/
	
	/** Sets the shadow of this platform. **/
	@Friendly void setShadow(PlatformShadow shadow) {
		this.shadow = shadow;
	}

}
